package com.googlecode.lookie.swing.tree.navigation;

import java.util.ArrayList;
import java.util.List;

import javax.swing.JTree;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

public abstract class Navigator
    implements TreeExpansionListener
{

    private JTree tree;

    private DefaultTreeModel rootModel;

    private DefaultTreeModel currentModel;

    private TreeNode root;

    private TreeNode currentRoot;

    private List<TreeNode> expandedNodes = new ArrayList<TreeNode>();

    private boolean monitorExpansion = true;

    public Navigator( JTree tree, TreeNode root )
    {

        this.tree = tree;
        this.root = root;
    }

    public void init()
    {

        currentRoot = root;
        rootModel = newTreeModel( root );

        tree.addTreeExpansionListener( this );
        tree.setModel( rootModel );
    }

    public void goTo( TreeNode node )
    {
        currentModel = newTreeModel( node );
        currentRoot = node;
        tree.setModel( currentModel );
        restoreExpandedNodes();
    }

    public void goUp()
    {
        TreeNode node = currentRoot.getParent();
        goTo( node );
    }

    public void goHome()
    {
        goTo( root );
    }

    public boolean canGoUp()
    {
        return currentRoot != root;
    }

    protected abstract DefaultTreeModel newTreeModel( TreeNode root );

    private void restoreExpandedNodes()
    {
        monitorExpansion = false;
        for ( TreeNode node : expandedNodes )
        {
            if ( isAncestor( currentRoot, node ) )
            {
                tree.expandPath( new TreePath( currentModel.getPathToRoot( node ) ) );
            }
        }
        monitorExpansion = true;
    }

    private boolean isAncestor( TreeNode ancestor, TreeNode child )
    {
        if ( child == null )
        {
            return false;
        }
        if ( child.getParent() == null )
        {
            return false;
        }
        if ( child.getParent().equals( ancestor ) )
        {
            return true;
        }
        return isAncestor( ancestor, child.getParent() );
    }

    public void treeCollapsed( TreeExpansionEvent event )
    {
        if ( monitorExpansion )
        {
            TreeNode node = (TreeNode) event.getPath().getLastPathComponent();
            if ( expandedNodes.contains( node ) )
            {
                expandedNodes.remove( node );
            }
        }
    }

    public void treeExpanded( TreeExpansionEvent event )
    {
        if ( monitorExpansion )
        {
            TreeNode node = (TreeNode) event.getPath().getLastPathComponent();
            if ( !expandedNodes.contains( node ) )
            {
                expandedNodes.add( node );
            }
        }
    }

}
